from datetime import datetime
import difflib
import http
import json
import logging
import os
from openai import OpenAI
from tqsdk import TqApi, TqAuth,TqAccount
from concurrent.futures import ThreadPoolExecutor
import threading
from tqsdk.ta import *

class BybridData:
    dk:pd.DataFrame
    m60k:pd.DataFrame
    m15k:pd.DataFrame
    m5k:pd.DataFrame

class TradingContext:
    
    conn = http.client.HTTPConnection("127.0.0.1",19527)
    
    LOCK = threading.Lock()      # 线程锁
    
    strategy_data = {}

    instrument_limit = 1

    data_dirty = False

    def __init__(self,instrument_limit=1):

        self.ds = OpenAI(api_key="xxxx", base_url="https://api.lkeap.cloud.tencent.com/v1")
        self.model = "deepseek-r1"
        self.instrument_limit = instrument_limit
        self.perist_file = f"persist/trading_data.json"
        self.load_runtime_data()
        self.env_msg=[
            {"role": "system", "content": "你是一个期货分析员，你需要根据数据给出最优持仓系数，注意您只能输出：-1到+1的一个浮点数代表当前最佳持仓比例,-1(满仓空单),+1(空仓),+1(满仓多单)，不可以输出其他描述信息"},
            {"role": "system", "content": "我可以为你提供以下数据：当前持仓P；当前获利X；开平持仓量（open_oi，close_oi） ；最近20天的K线数据；1小时级别数据以便于你判断趋势及15分钟数据便于你分析阻力支撑；5分钟数据以便于你分析买卖点"},
            {"role": "system", "content": "注意你只能通过上面提供的数据不能基于假设做出判断，避免频繁交易"}
        ]

    def get_position(self,code:str)->float:
        try:
            # 发送 GET 请求
            self.conn.request("GET", "/get_position?code="+code)
            # 获取响应
            response = self.conn.getresponse()
            # 读取响应内容
            response_data = response.read()
            print(f"Status: {response.status} {response.reason} Response:{response_data.decode("utf-8")}")
            result = json.loads(response_data)
            return float(result[code])
        except Exception as e:
            print("Error:", e)

        finally:
            # 关闭连接
            self.conn.close()
        return .0


    def sync_position(self,code:str,ratio:float)->bool:
        # 请求头和 POST 数据
        head_data = {"Content-Type": "application/json"}
        req_data = json.dumps({
            "code": code,
            "ratio": ratio
        })
        try:
            # 发送 POST 请求
            self.conn.request("POST", "/sync_position", body=req_data, headers=head_data)
            # 获取响应
            response = self.conn.getresponse()
            # 读取响应内容
            response_data = response.read()
            print(f"Status: {response.status} {response.reason} Response:{ response_data.decode("utf-8")}")
            result = json.loads(response_data)
            return result["result"] == "Success"
        except Exception as e:
            print("Error:", e)
        finally:
            # 关闭连接
            self.conn.close()
        return False



    def set_target_position(self,symbol:str,ratio:float):
        if ratio != 0 and self.is_instrument_limit(symbol):
            logging.warning(f"position limit : {symbol} -> {ratio} -> {self.get_instrument_number()}")
            return False
        self.sync_position(symbol,ratio)
        self.data_dirty = True
        return True
    
    def get_instrument_number(self):
        result = 0
        for symbol in self.strategy_data:
            position = self.get_position(symbol)
            if position != 0:
                result+=1
        return result

    def reasoning_signal(self,msg:str,last_msg:str,last_result:float):
        
        req_msg = self.env_msg.copy()
        if last_msg :
            req_msg.append({'role': 'user', 'content': last_msg})
        if last_msg and last_result is not None:
            req_msg.append({'role': 'assistant', 'content': f'{last_result}'})
        #print(self.last_msg)
        req_msg.append({'role': 'user', 'content': msg})
        response = self.ds.chat.completions.create(
            model=self.model,
            #model="deepseek-r1:32b",
            messages=req_msg,
            stream=False,
            temperature=1
        )
        print(response.choices[0].message.content)
        last_result = float(response.choices[0].message.content.strip())
        if response.choices[0].message.reasoning_content:
            logging.info(f"{datetime.now()}:position-> {response.choices[0].message.content} -> {last_result} reasoning_content:{response.choices[0].message.reasoning_content}")

        return last_result

    def load_runtime_data(self):
        #加载运行时数据
        if os.path.exists(self.perist_file):
            with open(self.perist_file, 'r') as file:
                data = json.load(file)
                self.strategy_data = data["strategy_data"]
                
    def get_strategy_data(self,symbol):
        if symbol in self.strategy_data:
            return self.strategy_data[symbol]
        return None

    def save_runtime_json(self):
        #保存运行时数据
        data = {}
        data["strategy_data"] = self.strategy_data
        with open(self.perist_file, 'w') as file:
            json.dump(data, file)
        self.data_dirty = False


    def set_strategy_data(self,symbol,data:dict):
        self.strategy_data[symbol] = data
        self.data_dirty = True

    def is_instrument_limit(self,symbol):
        position = self.get_position(symbol)
        if position != .0:
            return False
        return self.get_instrument_number() >= self.instrument_limit

    def update(self):
        if self.data_dirty :
            self.save_runtime_json()
    
    def __del__(self):
        pass
# 策略参数配置

class TradingStrategy:

    def __init__(self,symbol,tqsdk:TqApi,context:TradingContext,loss_point:int = 10,correlation:float=0.75):
        self.api = tqsdk 
        self.context = context
        self.loss_point = loss_point
        self.symbol = symbol
        self.correlation = correlation
        self.is_reasoning = False
        # 加载运行时变量
        self.perist_file = f"persist/trading_strategy_{symbol}.json"
        self.position:float = .0        # 实际持仓方向
        self.entry_price:float = .0     # 入场价格
        self.highest_high:float = .0    # 多仓最高价
        self.lowest_low = float('inf')  # 空仓最低价
        self.open_time = None      # 开仓时间戳
        self.current_profit = 0
        self.ask_close = False
        self.last_msg:str= None
        self.last_result:int = 0
        self.load_strategy_data()
        # 初始化数据
        self.quote = self.api.get_quote(symbol)
        self.position = context.get_position(symbol)
        self.ts = self.api.get_trading_status(symbol)
        self.data = BybridData()
        # 各周期K线数据（排除未完成K线）
        self.data.dk = self.api.get_kline_serial(symbol,86400,30)[:-1]
        self.data.m60k = self.api.get_kline_serial(symbol,3600,120)
        self.data.m15k = self.api.get_kline_serial(symbol,900,120)
        self.data.m5k = self.api.get_kline_serial(symbol,300,120)
        

    def load_strategy_data(self):
        #加载运行时数据
        data = self.context.get_strategy_data(self.symbol)
        if data is not None:
            self.highest_high = data["highest_high"]
            self.lowest_low = data["lowest_low"]
            self.entry_price = data["entry_price"]
            if "open_time" in data:
                self.open_time = datetime.strptime(data["open_time"], "%Y-%m-%d %H:%M:%S")
            if "last_msg" in data:
                self.last_msg = data["last_msg"] 
            if "last_result" in data:
                self.last_result = data["last_result"]

    def save_strategy_data(self):
        #保存运行时数据
        data = {}
        data["highest_high"] = self.highest_high
        data["lowest_low"] = self.lowest_low
        data["entry_price"] = self.entry_price
        if self.open_time:
            data["open_time"] = self.open_time.strftime("%Y-%m-%d %H:%M:%S")
        if self.last_msg:
            data["last_msg"] = self.last_msg
        if self.last_result:
            data["last_result"] = self.last_result
        self.context.set_strategy_data(self.symbol,data)

    def check_stop_loss(self):

        """止损条件检查"""
        last_price = self.quote.last_price
        price_tick = self.quote.price_tick
        
        # 计算浮动盈亏 
        if self.position > 0:
            self.current_profit = (last_price - self.entry_price) / self.quote.price_tick
            if last_price > self.highest_high:
                self.highest_high = last_price
                self.save_strategy_data()
            drawdown = self.highest_high - last_price
            if drawdown >= self.loss_point * price_tick:
                self.set_target_position(0,"long stop")
            elif drawdown >= self.loss_point * price_tick/2:
                self.ask_close = True
            else:
                self.ask_close = False
                
        elif self.position < 0:
            self.current_profit = (self.entry_price - last_price) / self.quote.price_tick
            if last_price < self.lowest_low:
                self.lowest_low = last_price
                self.save_strategy_data()
            drawdown = last_price - self.lowest_low
            if drawdown >= self.loss_point * price_tick:
                self.set_target_position(0,"short stop")
            elif drawdown >= self.loss_point * price_tick/2:
                self.ask_close = True
            else:
                self.ask_close = False

    def set_target_position(self, ratio, reason):
        """执行开仓操作"""
        with self.context.LOCK:
            if not self.context.set_target_position(self.symbol,ratio):
                return
            if ratio != .0:
                self.entry_price = self.quote.last_price  # 记录入场价格       
                self.open_time = self.get_current_time()  # 记录开仓时间
                self.position = ratio
                self.ask_close = False
                # 初始化极值价格
                if ratio > .0:
                    self.highest_high = self.entry_price
                elif ratio < .0:
                    self.lowest_low = self.entry_price
                logging.info(f"[{self.open_time.strftime('%H:%M:%S')}] open | direction: {'long' if ratio>0 else 'short'} | price: {self.entry_price:.2f}")
            else:
                # 重置状态
                self.position = .0
                self.open_time = None
                self.highest_high = 0
                self.current_profit = 0
                self.lowest_low = float('inf')
                self.ask_close = False
                # 记录平仓信息
                if self.open_time :
                    holding_duration = (self.get_current_time() - self.open_time).total_seconds() / 60
                    logging.info(f"[{self.quote.datetime}] {reason} | price: {self.quote.last_price:.2f} | use: {holding_duration:.1f}minutes")
            self.save_strategy_data()
                


    def get_current_time(self):
        return datetime.strptime(self.quote.datetime, "%Y-%m-%d %H:%M:%S.%f")

    def run_reasoning(self):
        try:
            self.is_reasoning = True
            logging.info(f"[{self.quote.datetime}] {self.symbol} reasoning_signal | hold: {self.position} | profit: {self.current_profit} | price:{self.quote.last_price}|跳：{self.quote.price_tick}")
            msg = self.get_msg()
            self.last_result = self.context.reasoning_signal(msg,self.last_msg,self.last_result)
            self.last_msg = msg
            
            #signal = random.randint(-2,3)
            self.is_reasoning = False

            self.set_target_position(self.last_result,"signal")

            logging.info(f"[{self.quote.datetime}] {self.symbol} handle_signal | hold: {self.position} | profit: {self.current_profit} | price:{self.quote.last_price}|跳：{self.quote.price_tick}\n")
            
        except Exception as e:
            print("Error:", e)
        finally:
            self.save_strategy_data()
        

    def is_need_reasoning(self):
        if self.is_reasoning or self.ts.trade_status != "CONTINOUS":
            return False
        if self.position != 0 and self.current_profit > 0 and not self.ask_close:
            return False
        if self.context.is_instrument_limit(self.symbol) and self.position==.0:
            return False
        if self.last_msg :
            diff_ratio = difflib.SequenceMatcher(None, self.last_msg, self.get_msg()).ratio()
            if diff_ratio>self.correlation:
                return False
        return True

    def update(self):
        """主运行循环"""
        if self.ts.trade_status == "CONTINOUS":
            # 更新极值价格
            if self.position != 0:
                self.check_stop_loss()
        
    def get_msg(self):
        #使用ds推理，并根据结果同步持仓
        dks = self.data.dk[["datetime","open", "high", "low", "close","volume","open_oi","close_oi"]].iloc[-20:]
        
        dma60m = DMA(self.data.m60k,5,10,20)
        sma60m = SMA(self.data.m60k,5,2)
        ema60m = EMA(self.data.m60k,9)
        boll60m = BOLL(self.data.m60k,26,2)
        macd60m = MACD(self.data.m60k,12,26,9)
        data60m = pd.concat([self.data.m60k[["open", "high", "low", "close","volume"]],dma60m, sma60m, ema60m, boll60m,macd60m], axis=1)


        ema15m = EMA(self.data.m15k,20)
        sma15m = SMA(self.data.m15k,50,2)
        boll15m = BOLL(self.data.m15k,20,2)
        rsi15m = RSI(self.data.m15k,14)
        data15m = pd.concat([self.data.m15k[["open", "high", "low", "close","volume"]],ema15m, sma15m, boll15m, rsi15m], axis=1)

        ema5m1 = EMA(self.data.m5k,9)
        macd5m = MACD(self.data.m5k,6,13,5)
        boll5m = BOLL(self.data.m5k,14,1.5)
        rsi5m = RSI(self.data.m5k,7)
        kdj5m = KDJ(self.data.m5k,5,3,3)
        atr5m = ATR(self.data.m5k,14)

        data5m = pd.concat([self.data.m5k[["open", "high", "low", "close","volume"]],ema5m1, boll5m,rsi5m,kdj5m,macd5m,atr5m], axis=1)
       
        last_msg = (
            f"当前持仓系数P:{self.position}；当前获利X:{self.current_profit}点"
            f"最近20日k线(last 20)：\n{dks.round(0).to_string(index=False)}\n\n"
            f"1小时周期数据(last 5)：\n{data60m.iloc[-5:].round(0).to_string(index=False)}\n\n"
            f"15分钟周期数据(last 5)：\n{data15m.iloc[-5:].round(0).to_string(index=False)}\n\n"
            f"5分钟周期数据(last 5)：\n{data5m.iloc[-5:].round(0).to_string(index=False)}\n\n"
        ) 
        return last_msg


if __name__ == "__main__":
    
    log_file = datetime.now().strftime("log/trading_%Y-%m-%d_%H%M%S.log")
    logging.basicConfig(filename=log_file, level=logging.INFO,encoding='utf-8') 
    tqsdk = TqApi(account=TqAccount("simnow", "xxxx", "xxxx"),auth=TqAuth("xxxx", "xxxx"))

    executor = ThreadPoolExecutor(max_workers=1)
    context = TradingContext(1)
    future = None
    strategy_list = [
        TradingStrategy("SHFE.rb2510",tqsdk,context,10,0.75),
        TradingStrategy("SHFE.bu2506",tqsdk,context,10,0.8),
    ]
    reasoning_iterator = 0
    
    while True:
        try:
            tqsdk.wait_update()
            
            for strategy in strategy_list:
                strategy.update()
            if future is None or future.done() :
                reasoning_strategy = strategy_list[reasoning_iterator]
                if reasoning_strategy.is_need_reasoning():   
                    # 提交信号生成任务
                    future = executor.submit(reasoning_strategy.run_reasoning)
                reasoning_iterator = (reasoning_iterator+1)%len(strategy_list)

            context.update()
            
        except Exception as e:
            print("Error:", e)
            context.save_runtime_json()
            break

    
    executor.shutdown()
    tqsdk.close()
